GeoSpatial I#
In this tutorial we are going to learn how to plot on a map the graffiti information we have collected so far. The map we are going to be creating will be very interactive so it will allow us to include different information and query our data.
import pandas as pd
from pathlib import Path
# creating a relative path to the data folder
pth = Path('../../data')
# read canvas csv file into a dataframe
canvas = pd.read_csv(pth / 'canvas.csv')
# read graffito
graffiti = pd.read_csv(pth /'graffiti.csv')
graffiti.columns
Index(['id', 'canvas_id', 'created_at', 'uploaded_at', 'title', 'num',
'date_recorded', 'width', 'height', 'type', 'technique', 'marker_type',
'nip_type', 'other', 'num_colors', 'colors', 'nature_graffiti',
'transcribable', 'message', 'transcription'],
dtype='object')
We want to create a single table that combines certain columns of the canvas and graffiti DataFrames.
keep_canvas = ['id', 'coords']
keep_graffiti = ['canvas_id', 'type']
from ast import literal_eval
from numpy import nan
def extract(row, key):
dct = literal_eval(row)
value = dct[key]
if value =='':
value = nan
return
else:
return float(value)
df = (canvas.loc[:,keep_canvas]
.assign(lat= canvas.coords.apply(extract, key='latitude')) #apply function and create a column with latitude
.assign(long=canvas.coords.apply(extract, key='longitude')) #apply function and create a column with latitude
.dropna(subset=['lat', 'long']) # remove any entries with no lat or long
.merge(graffiti.loc[:,keep_graffiti], left_on='id', right_on='canvas_id', how='inner')
)
FOLIUM#
We are all ready to plot our data!!! To do so, we are going to use one of the many packages that Python offers… Folium is actually a Python ‘wrapper’ to get access to a javascript package called leaflet. Javascript is de facto language used nowadays to display web content.
What do you mean by ‘wrapper’? Well, given that Python is considered to be one of the easiest programming languages, it is often used to ‘wrap’ around packages written in other languages which are much harder to learn and to deploy, like C or C++. This is good news for us as this means that a lot of very powerful tools are accessible through Python.
# Import folium as is
import folium
Generating a Basic map#
Let’s start by generating a very basic map in Folium. We are going to center it on Gaswork Park. Type the following,
# Gasworks
center_lat, center_lon = 47.645, -122.334
# Generate a base map
fmap = folium.Map(
location = [center_lat, center_lon], # coordinates on where to center your map
zoom_start=11, # zoom level. Higher the number the closer you are
width='60%',
min_zoom=11,
control_scale = True,
max_bounds = True, # keep twithin the bounds defined below
min_lat = 47.4960,
max_lat = 47.8000,
min_lon = -122.4376,
max_lon = -122.2196,
);
# display map
fmap
Adding a single canvas (= graffito)#
Next we will just add a randomly selected graffiti site to our map.
# select a random location
rg = df.sample(n=1, random_state=2024)
rg
| id | coords | lat | long | canvas_id | type | |
|---|---|---|---|---|---|---|
| 1565 | 433 | {'latitude': 47.65955, 'longitude': -122.31786... | 47.65955 | -122.317865 | 433 | tag |
Convert lat, long into a Python list.
# retrieve canvas location
loc = rg.iloc[0][['lat','long']].to_list()
loc
[np.float64(47.65955), np.float64(-122.317865)]
Make a Marker#
To generate a marker, we first need to generate an Icon. An icon accepts two parameters: color and icon (=icon type)
# make icon
icn = folium.Icon(
color = 'green',
icon ='pencil'
);
You can choose from the following colors:
red, blue, green, purple, orange, darkred, lightred, beige, darkblue, darkgreen, cadetblue, darkpurple, white, pink, lightblue, lightgreen, gray, black, lightgray.
# make marker
mrkr = folium.Marker(
location = loc,
icon = icn
);
Let’s recreate our base map
# Generate a map
fmap = folium.Map(
location = [center_lat, center_lon], # coordinates on where to center your map
zoom_start=11, # zoom level. Higher the number the closer you are
width='60%',
min_zoom=11,
control_scale = True,
max_bounds = False, # keep to the bounds defined below
min_lat = 47.4960,
max_lat = 47.8000,
min_lon = -122.4376,
max_lon = -122.2196
)
Add the marker created to the map
# add to map
mrkr.add_to(fmap);
#display map
fmap
Adding a Clustermarker#
If we want to display all of our sites, we quickly run into the situation where we have too many graffiti too close to each other and displaying all individually becomes too sluggish. Wouldn’t it be great that as we zoom in and out of our map, we are able to sum up how many graffiti we have at a location?
We can achieve this using a new object in Folium called a MarkerCluster. Basically as we zoom out and our markers get closer to each other, MarkerCluster will substitute individual markers for a single marker that contains all of the markers. It will actually tell us the number of markers at a location too!!
We can easily change the function we defined above so that instead of receiving a Folium map it receives a MarkerCluster. Our new function will not receive a Folium map but a MarkerCluster instead. First let’s create a MarkerCluster,
Let’s recreate our basemap,
# Generate a map
fmap = folium.Map(
location = [center_lat, center_lon], # coordinates on where to center your map
zoom_start=11, # zoom level. Higher the number the closer you are
width='60%',
min_zoom=11,
control_scale = True,
max_bounds = False, # keep to the bounds defined below
min_lat = 47.4960,
max_lat = 47.8000,
min_lon = -122.4376,
max_lon = -122.2196
)
Import the MarkerCluster from the plugins package and initialize a blank MarkerCluster
# import MarkerCluster
from folium.plugins import MarkerCluster
# Create a BLANK markercluster
marker_cluster = MarkerCluster(name='graffiti').add_to(fmap)
Populate the MarkerCluster with all of the canvas locations,
# add markers for each graffiti to our markercluster
for indx, row in df.iterrows():
# create marker
folium.Marker(location = [ row.lat, row.long ], # location of each graffiti
popup = row.type, # identify the popup with each type
icon = folium.Icon(color = 'red', icon='pencil') # create an icon
).add_to(marker_cluster)
# display map
fmap
Generating a HeatMap#
From a geospatial perspective,a heat map is a method of showing the geographic clustering of a phenomenon. In this case our phenomenon is graffiti. It is relatively easy to generate such a map one with Folium using the Folium’s object Heatmap. The Heatmap object Folium wants our locational data as a list of lat/lon coordinates.
We recreate our basemap
# Generate a map
fmap = folium.Map(
location = [center_lat, center_lon], # coordinates on where to center your map
zoom_start=11, # zoom level. Higher the number the closer you are
width='60%',
min_zoom=11,
control_scale = True,
max_bounds = False, # keep to the bounds defined below
min_lat = 47.4960,
max_lat = 47.8000,
min_lon = -122.4376,
max_lon = -122.2196
)
Extract all lat, long coordinates into a Python list,
# generate a list of graffiti locations
locs= df.loc[:, ['lat','long']].values.tolist()
locs[0:5]
[[47.658577, -122.317607],
[47.658577, -122.317607],
[47.658577, -122.317607],
[47.658577, -122.317607],
[47.661615, -122.320492]]
Initialize the heatmap with all canvas coordinates and add to the base mape
# add heat map
from folium.plugins import HeatMap
HeatMap(data = locs,
radius = 10,
gradient= {1.0: 'red', 0.75: 'violet', 0.5: 'orange', 0.25:'yellow'},
overlay = False,
).add_to(fmap);
# display map
fmap
Putting all together: layers#
Finally, we may want to have several layers: one with a ClusterMarker, and another with a Heatmap. This is actually possible
from folium.plugins import MarkerCluster, HeatMap, FeatureGroupSubGroup
# Gasworks
lat, lon = 47.645, -122.334
# generate a base map
fmap = folium.Map(
location = [lat, lon],
zoom_start=11,
width='60%',
min_zoom=11,
control_scale = True,
max_bounds = True,
min_lat = 47.4960,
max_lat = 47.8000,
min_lon = -122.4376,
max_lon = -122.2196
)
# create a feature group to store all subgroups
all = folium.FeatureGroup(name='All').add_to(fmap)
Graffiti markercluster (sub group)#
# create a Feature Sub Group for the graffiti markercluster
graf_sg = FeatureGroupSubGroup(all, 'Graffiti').add_to(fmap)
# create a blank cluster marker
marker_cluster = MarkerCluster(name='Graffiti').add_to(graf_sg)
# add markers for each graffiti to our markercluster
for indx, row in df.iterrows():
# create marker
folium.Marker(location = [ row.lat, row.long ], # location of each graffiti
popup = row.type, # identify the popup with each type
icon = folium.Icon(color = type2color[row.type], icon='pencil') # create an icon
).add_to(marker_cluster)
---------------------------------------------------------------------------
NameError Traceback (most recent call last)
Cell In[34], line 6
1 # add markers for each graffiti to our markercluster
2 for indx, row in df.iterrows():
3 # create marker
4 folium.Marker(location = [ row.lat, row.long ], # location of each graffiti
5 popup = row.type, # identify the popup with each type
----> 6 icon = folium.Icon(color = type2color[row.type], icon='pencil') # create an icon
7 ).add_to(marker_cluster)
NameError: name 'type2color' is not defined
Graffiti heatmap (sub group)#
# create a Feature SubGroup for the graffiti heatmap
heatmap_sg = FeatureGroupSubGroup(all, 'Heatmap').add_to(fmap)
# create a heatmap
heat_map = HeatMap(data = locs,
radius = 10,
gradient= {1.0: 'yellow', 0.75: 'orange', 0.5: 'red', 0.25:'violet'},
overlay = False).add_to(heatmap_sg)
Add layer control#
# create a layer control
lc = folium.LayerControl().add_to(fmap)
# display map
fmap